package com.ctfo.ulcp.exp.core

import com.ctfo.ulcp.exp.db.LLMModel
import com.ctfo.ulcp.exp.utils.JsonCodec
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.type.TypeReference
import kotlinx.coroutines.future.await
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.regex.Pattern

data class PyOutput(val output: String, val metadata: Any?, val callbackEvents: Any?) {
    @Suppress("unused")
    constructor() : this("", null, null)

    fun outJson(): String {
        val pattern = Pattern.compile(".*```json(.+)```.*", Pattern.DOTALL or Pattern.MULTILINE)
        val matcher = pattern.matcher(output)
        return if (matcher.matches()) {
            matcher.group(1)
        } else {
            output
        }
    }
    fun <T> jsonValue(type: TypeReference<T>): T {
        return JsonCodec.decodeValue(outJson(), type)
    }
}

/**
 * 模型可以是 tongyi 或 openai
 */

data class Input(
    val model: String,
    @get:JsonProperty("user_input") val userInput: String,
    @get:JsonProperty("db_info") var dbInfo: String? = null,
    @get:JsonProperty("interface_info") var interfaceInfo: String? = null,
    @get:JsonProperty("context") var context: String? = null
)

data class DataItem(val id: Int, val name: String, val desc: String) {
    @Suppress("unused")// 用于JSON反序列化
    constructor() : this(0, "", "")
}

data class DataItems(val interfaces: List<DataItem>, @get:JsonProperty("datasources") val dataSources: List<DataItem>)

data class Param(val input: Input, val config: Map<String, String> = mapOf())

object PyCaller {

    private val logger = LoggerFactory.getLogger("com.ctfo.ulcp.Caller")
    private val client: HttpClient
    private const val BASE_URL = "http://172.20.72.132:8000"


    init {
        val executorService = Executors.newVirtualThreadPerTaskExecutor()
        client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5))
            .executor(executorService)
            .build()
    }

    fun callFilter(
        model: String = "openai",
        userInput: String,
        dataItems: DataItems
    ): CompletableFuture<DataItems> {

        val param = mapOf(
            "input" to mapOf(
                "model" to model,
                "user_input" to userInput,
                "data" to JsonCodec.encode(dataItems)
            )
        )
        return call("filter/invoke", param).thenApply { py ->
//            val type = object : TypeReference<Map<String, List<DataItem>>>() {}
//            py.jsonValue(type)

            JsonCodec.decodeValue(py.outJson(), DataItems::class.java)
        }
    }


    fun callRoute(
        model: LLMModel = LLMModel.OpenAI,
        userInput: String,
        businessSummary: String,
        docSummary: String,
        dataSummary: String,
        apiSummary: String
    ): CompletableFuture<String> {
        val param = mapOf<String, Any>(
            "input" to mapOf(
                "model" to model.code,
                "user_input" to userInput,
                "business_summary" to businessSummary,
                "doc_summary" to docSummary,
                "data_summary" to dataSummary,
                "interface_summary" to apiSummary,
            )
        )
        return call("route/invoke", param).thenApply { py ->
            val routeInfo = JsonCodec.decodeValue(py.output)
            routeInfo["route"] as String
        }
    }


    fun callQuestion(model: LLMModel, userInput: String): CompletableFuture<PyOutput> {
        val input = Input(model.code, userInput)
        return call("chat/invoke", Param(input))
    }

    fun callDoc(model: LLMModel, userInput: String, context: String): CompletableFuture<PyOutput> {
        val input = Input(model.code, userInput, context = context)
        return call("doc/invoke", Param(input))
    }

    fun callCode(
        model: LLMModel,
        userInput: String,
        dbInfo: String,
        apiInfo: String
    ): CompletableFuture<PyOutput> {
        val input = Input(model.code, userInput).apply {
            this.dbInfo = dbInfo
            this.interfaceInfo = apiInfo
        }
//        return call("coder_test/invoke", Param(input))
        return call("coder/invoke", Param(input))
    }


    private fun call(path: String, param: Any): CompletableFuture<PyOutput> {
        val uri = URI.create("$BASE_URL/$path")
        val req = JsonCodec.encode(param)

        val request = HttpRequest.newBuilder()
            .uri(uri)
            .header("Content-Type", "application/json")
            .timeout(Duration.ofSeconds(60))
            .POST(HttpRequest.BodyPublishers.ofString(req))
            .build()

        val t0 = System.currentTimeMillis()
        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply {
                val resp = it.body()
                logger.debug(
                    "http request done.path:{}\nreq:{}\nresp:{}\ntime:{}",
                    path,
                    req,
                    resp,
                    System.currentTimeMillis() - t0
                )
                JsonCodec.decodeValue(resp, PyOutput::class.java)
            }
    }

}


fun testQuestion(): CompletableFuture<PyOutput> {
    return PyCaller.callQuestion(LLMModel.OpenAI, userInput = "请介绍一下千方科技")
}

fun testFilter(): CompletableFuture<DataItems> {

    val dataItems = DataItems(
        interfaces = listOf(
            DataItem(1, "t_vehicle", "车辆基本信息表"),
            DataItem(2, "t_vehicle_rel", "车辆和公司所属关系表"),
            DataItem(3, "t_user", "用户表"),
            DataItem(4, "t_org", "组织机构表"),
        ),
        dataSources = listOf(
            DataItem(5, "/queryVehicleTrace", "查询车辆轨迹"),
            DataItem(5, "/queryVehicleLaw", "查询车辆缴费历史"),
            DataItem(6, "/sendMail", "发送邮件"),
            DataItem(6, "/checkVehicleExists", "检查车辆是否入网"),
        )
    )

    return PyCaller.callFilter("openai", "千方科技下属车辆最近3天有哪些车辆到达过天津市?", dataItems)
}

fun testRoute(): CompletableFuture<String> {
    return PyCaller.callRoute(
//        userInput = "迎宾大道上最近3天有多少疲劳驾驶报警?",
        userInput = "昨天酒后驾驶闯红灯了应该怎么处理?",
//        userInput = "重货系统的详细设计给我介绍一下?",
        businessSummary = """
        已知, 本系统包含的功能列表如下:
        1 车辆基础信息查询: 根据"问题"中的条件查询车辆的基础信息, 例如车牌号，车辆品牌、颜色，车主、车辆所属公司等。
        2 车辆位置查询: 根据具体的车牌号查询对应车辆在某个时刻所处的位置
        3 车辆的历史轨迹查询: 根据具体的车牌号和"问题"中涉及到的时间范围, 查询车辆在某段时间内的运行轨迹
        4 车辆的历史报警和预警信息查询: 根据"问题"中涉及到的时间点或时间范围, 查询符合条件的车辆存在的报警或预警信息
        5 车辆的历史报警和预警信息统计: 根据条件统计报警或预警次数, 条件可能是预警时间、区域、预警或报警种类等
        6 车辆信息统计表: 根据"问题"统计车辆相关的数据汇总情况, 不限定结果的显示方式
        7 车辆信息统计图: 根据"问题"统计车辆相关的数据汇总情况, 并将结果以图表形式显示
        8 停留信息查询: 根据车牌号和时间范围查询一辆车的停车或停留信息
        9 车辆入网验证: 根据车牌号判断指定车辆是否在本系统中注册
        """.trimIndent(),
        docSummary = """
        重货系统设计文档： 包含系统的详细设计、功能说明
        """.trimIndent(),
        dataSummary = """
        重货系统数据库: 包含车辆基本信息、车辆报警信息、车辆位置信息    
        """.trimIndent(),
        apiSummary = """
        车辆行驶轨迹查询： 根据车牌号查询车辆行驶轨迹
        车辆位置查询： 根据车牌号查询车辆的实时位置
        """.trimIndent()
    )
}

fun testDoc(): CompletableFuture<PyOutput> {
    val context = """
    姓名	程广艺	职位
    起止时间	工作内容	完成情况
    2022/9/27	安全漏洞修复	完成
    2022/9/28	安全漏洞修复	完成
        系统功能确认、安全管理系统更新	完成
    2022/9/29	安全漏洞修复	完成
        809主动安全报警联调bug修改	完成
    2022/9/30	非现场治超告知函导出修改，高速门架、收费站流量统计，9月30漏洞确认	完成
    2022/10/8	肇庆研发任务梳理和系统功能检查、安全生产系统更新、治超短信配置功能确认	完成
    2022/10/9	16服务器网络故障原因调查、防火墙端口设置、项目任务安排、安全生产系统更新、国庆高速流量分车型统计等	完成
    2022/10/10	出租车系统账号密码修改、oa访问故障排查、服务器功能巡查	完成
    """.trimIndent()

    return PyCaller.callDoc(
        LLMModel.OpenAI,
        userInput = "程广艺2022年10月8日到10月10日完成了什么工作",
        context = context
    )
}

fun testCoder(): CompletableFuture<PyOutput> {
    val dbInfo = """
    mysql datasource ID: 1228
    表名: scores - 学生分数表
    字段: id(long) - 自增主键
    student_number(varchar) - 学生学号
    score(decimal) - 学生分数
    examination_time(datetime) - 考试时间
    examination_subject(varchar) - 考试学科

    表名: student - 学生信息表
    字段: id(long) - 自增主键
    name(varchar) - 学生姓名
    number(varchar) - 学生学号
    parent_name(varchar) - 父母名称
    email(varchar) - 邮箱地址
    """.trimIndent()
    println(dbInfo)


    val apiInfo = """
    interface ID: 4587
    用途: 发送邮件到指定地址
    请求格式: json
    请求字段描述: email(string) - 接收邮箱地址
    title(string) - 邮件标题
    content(string) - 邮件正文
    请求举例: {"email":"to email address", "title":"email title", "content":"email content"}
    响应格式: string
    响应字段描述: 发送邮件结果
    """.trimIndent()


    val question =
        "获取Alice同学的邮件地址，并且给Alice同学发一封邮件，邮件的标题是: 欢迎Alice同学， 邮件的内容是：欢迎Alice同学加入班级"

//    val question =
        "获取Alice同学的分数，并且给Alice同学发一封邮件，邮件的标题是: Alice同学分数通知， 邮件的内容是：Alice本次考了多少分"
    return PyCaller.callCode(LLMModel.OpenAI, userInput = question, dbInfo = dbInfo, apiInfo = apiInfo)
}

fun main() = runBlocking {
    println(testCoder().await())
//    println(testRoute().await())
//    println(testFilter().await())
}